//-------------------------------------------------------------------
//	Copyright (c) 2013  Zhirnov Andrey
//	This file is part of the "Noise Library" project.
//	See copyright notice in "linNoiseCL.h".
//-------------------------------------------------------------------

#pragma once

#include "../CSource.h"
#include "GeneratorUtils.h"
#include "GeneratorNativeUtils.h"

namespace Noise
{
	
    const real DEFAULT_RIDGED_FREQUENCY = 1.0;
    const real DEFAULT_RIDGED_LACUNARITY = 2.0;
    const int DEFAULT_RIDGED_OCTAVE_COUNT = 6;
    const ENoiseQuality DEFAULT_RIDGED_QUALITY = QUALITY_STD;
    const int DEFAULT_RIDGED_SEED = 0;
    const int RIDGED_MAX_OCTAVE = 30;


	
	//
	// Function Source for Ridged Multi Noise generation
	//

	class CSourceRidgedMulti : public CSource
	{
	// types
	protected:
		typedef CSourceRidgedMulti		Self;

		typedef static_array<real, RIDGED_MAX_OCTAVE>	spherical_weights_t;

	// variables
	protected:
		real			_frequency;		// Frequency of the first octave.
		real			_lacunarity;	// Frequency multiplier between successive octaves.
		ENoiseQuality	_noiseQuality;	// Quality of the Perlin noise.
		int				_octaveCount;	// Total number of octaves that generate the ridged-multifractal noise.
		int				_seed;			// Seed value used by the Perlin-noise function.
		
		static const
			ESource		_sourceType = RIDGED_MULTI;


	// methods
	public:
		CSourceRidgedMulti () :
			_frequency(DEFAULT_RIDGED_FREQUENCY),
			_lacunarity(DEFAULT_RIDGED_LACUNARITY),
			_noiseQuality(DEFAULT_RIDGED_QUALITY),
			_octaveCount(DEFAULT_RIDGED_OCTAVE_COUNT),
			_seed(DEFAULT_RIDGED_SEED)
		{
		}

		~CSourceRidgedMulti ()
		{}

		ESourceState GetState () const
		{
			return SOURCE_COMPLETE;
		}
		
		virtual void Delete ()
		{
		}

		virtual EResult GetInteger (ESourceQuery type, int *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case SOURCE_STATE :
					v[0] = GetState();
					return OK;

				case SOURCE_TYPE :
					v[0] = _sourceType;
					return OK;

				case SOURCES_COUNT :
					v[0] = 1;
					return OK;

				case INPUT_SOURCES :
					v[0] = 0;
					return OK;

				case MAX_OCTAVES :
					v[0] = RIDGED_MAX_OCTAVE;
					return OK;
			}
			return INVALID_ENUM;
		}

		virtual EResult SetArg (EArgument type, const int *v, int count)
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case ARG_OCTAVE_COUNT	: _octaveCount = Min( v[0], BILLOW_MAX_OCTAVE );	return OK;
				case ARG_NOISE_QUALITY	: _noiseQuality = (ENoiseQuality)v[0];				return OK;
				case ARG_SEED			: _seed = v[0];										return OK;
			}
			return INVALID_ENUM;
		}

		virtual EResult SetArg (EArgument type, const real *v, int count)
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case ARG_FREQUENCY		: _frequency = v[0];	return OK;
				case ARG_LACUNARITY		: _lacunarity = v[0];	return OK;
			}
			return INVALID_ENUM;
		}
		
		virtual EResult GetArg (EArgument type, int *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case ARG_OCTAVE_COUNT	: v[0] = _octaveCount;	return OK;
				case ARG_NOISE_QUALITY	: v[0] = _noiseQuality;	return OK;
				case ARG_SEED			: v[0] = _seed;			return OK;
			}
			return INVALID_ENUM;
		}
		
		virtual EResult GetArg (EArgument type, real *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case ARG_FREQUENCY		: v[0] = _frequency;	return OK;
				case ARG_LACUNARITY		: v[0] = _lacunarity;	return OK;
			}
			return INVALID_ENUM;
		}
		
		virtual CSourcePtr Clone () const override
		{
			Self *	src = new Self();

			src->_frequency		= _frequency;
			src->_lacunarity	= _lacunarity;
			src->_noiseQuality	= _noiseQuality;
			src->_octaveCount	= _octaveCount;
			src->_seed			= _seed;

			return src;
		}
		
		virtual void PreBuild (const TPreBuildData &data) override
		{
		}

		virtual bool BuildFunction (TFunctionData &data) override
		{
			Utils::NoiseGenFunctions( data );

			spherical_weights_t weights = CalcSpectralWeights( _lacunarity );

			if ( data.AddSourceType( _sourceType ) )
			{
				data.source << TOSTRING(
					real RidgedMulti (real3 vPoint, real _frequency, real _lacunarity,\n
									  int _noiseQuality, int _octaveCount, int _seed,\n
									  const real weights[30])\n
					{\n
						real3 p		= vPoint * _frequency;\n
						real signal = 0.0;\n
						real value  = 0.0;\n
						real weight = 1.0;\n
						real offset = 1.0;\n
						real gain	= 2.0;\n
						\n
						for (int octave = 0; octave < _octaveCount; octave++)\n
						{\n
							real3 n = MakeInt32Range( p );\n
							\n
							int seed = (_seed + octave) & 0x7fffffff;\n
							signal = GradientCoherentNoise3D( n, seed, _noiseQuality );\n
							\n
							signal = fabs( signal );\n
							signal = offset - signal;\n
							signal *= signal;\n
							signal *= weight;\n
							weight = signal * gain;\n
							\n
							if ( weight > 1.0 )	weight = 1.0;\n
							if ( weight < 0.0 )	weight = 0.0;\n
							\n
							value += (signal * weights[octave]);\n
							\n
							p *= _lacunarity;\n
						}\n
						\n
						return (value * (real)(1.25)) - (real)(1.0);\n
					}\n\n
				);
			}

			CHECK_VALUE( not weights.Empty() );

			data.source
				<< "real " << CSource::FunctionName() << _id << "(real3 vPoint)\n{\n"
				<< "const real weights[" << weights.Count() << "] = { ";

			FOR( i, weights )
			{
				data.source << '\n' << weights[i] << ',';
			}
			data.source.PopBack();
			data.source
				<< "};\n"
				<< "return RidgedMulti( vPoint, "
				<< _frequency << ", " << _lacunarity << ", " << _noiseQuality << ", "
				<< _octaveCount << ", " << _seed << ", weights );\n}\n";
			
			return true;
		}
		
		NATIVE(
		template < typename Real, Real (CSource::*NativeFunction)(const TVec<Real,3> &) >
		Real NativeFunction (const TVec<Real,3> &vPoint)
		{
			using namespace NativeUtils;

			typedef TVec<Real,3>	Real3;
			
			uni_array<const real> weights = GetCachedSpectralWeights( _lacunarity );

			Real3 p		= vPoint * _frequency;
			Real signal = 0.0;
			Real value  = 0.0;
			Real weight = 1.0;
			Real offset = 1.0;
			Real gain	= 2.0;

			for (int octave = 0; octave < _octaveCount; octave++)
			{
				Real3 n = MakeInt32Range( p );

				int seed = (_seed + octave) & 0x7fffffff;
				signal = GradientCoherentNoise3D( n, seed, _noiseQuality );

				signal = Abs( signal );
				signal = offset - signal;
				signal *= signal;
				signal *= weight;
				weight = signal * gain;

				if ( weight > 1.0 )	weight = 1.0;
				if ( weight < 0.0 )	weight = 0.0;

				value += (signal * weights[octave]);

				p *= _lacunarity;
			}

			return (value * Real(1.25)) - Real(1.0);
		}

		virtual float32 NativeFunction32 (const fvec3 &vPoint) override
		{
			return NativeFunction< float32, &CSource::NativeFunction32 >( vPoint );
		}

		virtual float64 NativeFunction64 (const dvec3 &vPoint) override
		{
			return NativeFunction< float64, &CSource::NativeFunction64 >( vPoint );
		})

		// Contains the spectral weights for each octave.
		static spherical_weights_t CalcSpectralWeights (real lacunarity)
		{
			spherical_weights_t result;
			
			real h = 1.0;
			real frequency = 1.0;

			FOR( i, result )
			{
				result[i] = Pow( frequency, -h );
				frequency *= lacunarity;
			}

			return result;
		}

		static uni_array<const real> GetCachedSpectralWeights (real lacunarity)
		{
			// TODO: thread safe code
			static spherical_weights_t	weights			= CalcSpectralWeights( DEFAULT_RIDGED_LACUNARITY );
			static real					lastLacunarity	= DEFAULT_RIDGED_LACUNARITY;

			if ( lacunarity != lastLacunarity )
			{
				weights = CalcSpectralWeights( lacunarity );
				lastLacunarity = lacunarity;
			}
			return uni_array<const real>( weights );
		}

		static bool CreateSource (CSourcePtr &src, CContext *context, ESource type)
		{
			ASSERT( type == _sourceType );

			src = new Self();
			return true;
		}
	};

}	// Noise